package com.allwees.bs.module.core.datapermission;

import com.allwees.bs.core.modelbase.branch.BranchUser;
import com.allwees.bs.core.modelbase.branch.BranchUserContext;
import com.allwees.bs.module.core.datapermission.entity.PermissionRule;
import com.allwees.core.common.util.reflect.ReflectionUtils;
import com.allwees.core.dao.configuration.datapermission.DataPermissionUtil;
import com.allwees.core.dao.configuration.datapermission.PermissionSqlProcessor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.Join;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SubSelect;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.springframework.stereotype.Component;

import java.sql.Connection;

/**
 * 数据权限拦截器 - 按预设的权限规则({@link PermissionRule})，针对当前用户添加数据权限SQL片段
 *
 * @author yangylsky
 * @since 2019-12-27
 *
 */
@Slf4j
@Intercepts({
	@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})
})
@Component
public class PermissionInterceptor implements Interceptor, PermissionSqlProcessor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		Object target = invocation.getTarget();
		if(target instanceof StatementHandler) {
			processStatementHandler((StatementHandler) invocation.getTarget());
		}
		return invocation.proceed();
	}

	private void processStatementHandler(StatementHandler statementHandler) {
		BoundSql boundSql = statementHandler.getBoundSql();
		if(!boundSql.getSql().contains(PermissionRule.TABLE_NAME)) { //跳过权限规则查询
			MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
					SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
			/**
			 * 先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，
			 *  然后就到BaseStatementHandler的成员变量mappedStatement
			 */
			MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
			if (!DataPermissionUtil.canProcessSql(mappedStatement.getId())) {
				return;
			}

			String permissionSql = null;
//			int TEST_COUNT  = 1000;
//			long start = System.nanoTime();
//			for(int i = 0; i < TEST_COUNT; i++) {
				permissionSql = processSql(mappedStatement.getSqlCommandType(), boundSql.getSql());
//			}
			// 通过反射修改sql语句
			ReflectionUtils.setFieldValue(boundSql, "sql", permissionSql);
//			log.info("process {} times: {}ms", TEST_COUNT, (System.nanoTime() - start) / 1000000);
		}
	}

	@Override
	public String processSql(SqlCommandType commandType, String sql) {
        BranchUser user = BranchUserContext.getUser();
        switch(commandType) {
			case INSERT:
				return processInsertSql(sql, user);
			case SELECT:
				return processSelectSql(sql, user);
			case UPDATE:
				return processUpdateSql(sql, user);
			case DELETE:
				return processDeleteSql(sql, user);
			default:
				return sql;
		}
	}

	private String processInsertSql(String sql, BranchUser user) {
		return sql;
	}

	private String processUpdateSql(String sql, BranchUser user) {
		return sql;
	}

	private String processSelectSql(String sql, BranchUser user) {
		if(user == null) {
			return sql;
		}
		try {
			String replaceSql = null;
			Select select = (Select) CCJSqlParserUtil.parse(sql);
			PlainSelect selectBody = (PlainSelect) select.getSelectBody();
			String mainTable = null;
			if(selectBody.getFromItem() instanceof Table) {
				mainTable = ((Table) selectBody.getFromItem()).getName().replace("`", "");
			} else if(selectBody.getFromItem() instanceof SubSelect) {
				replaceSql = processSelectSql(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), user);
			}
			if(StringUtils.isNotBlank(replaceSql)) {
				sql = sql.replace(((SubSelect) selectBody.getFromItem()).getSelectBody().toString(), replaceSql);
			}
			String mainTableAlias = null;
			try {
				mainTableAlias = selectBody.getFromItem().getAlias().getName();
			} catch(Exception e) {
				if(log.isDebugEnabled()) {
					log.debug("当前sql中，{} 没有设置别名", mainTable);
				}
			}

			String condExpr = null;
			PermissionRule realRuls = null;
			for(PermissionRule rule : RuleCache.RULES) {
				for(String role : user.getRoleNames()) {
					if(!ArrayUtils.contains(rule.getRoleNames(), role)) {
						continue;
					}
					if(ArrayUtils.contains(rule.getTableNames(), mainTable)) { // 若主表匹配规则主体，则应用本规则
						realRuls = rule;
						condExpr = _processPlaceholder(rule.getExpression(), mainTable, mainTableAlias, user);
						if(StringUtils.isNotBlank(condExpr)) {
							//将SQL片段加到WHERE条件中
							if(selectBody.getWhere() == null) {
								selectBody.setWhere(CCJSqlParserUtil.parseCondExpression(condExpr));
							} else {
								AndExpression and = new AndExpression(selectBody.getWhere(), CCJSqlParserUtil.parseCondExpression(condExpr));
								selectBody.setWhere(and);
							}
						}
					}

					//接下来检查JOIN表是否匹配规则
					try {
						String joinTable = null;
						String joinTableAlias = null;
						for(Join join : selectBody.getJoins()) {
							joinTable = ((Table) join.getRightItem()).getName();
							if(ArrayUtils.contains(rule.getTableNames(), joinTable)) {
								realRuls = rule;
								joinTableAlias = join.getRightItem().getAlias() == null ? null : join.getRightItem().getAlias().getName();
								condExpr = _processPlaceholder(rule.getExpression(), joinTable, joinTableAlias, user);
								if(StringUtils.isNotBlank(condExpr)) {
									//将SQL片段加到JOIN ON条件中
									if(join.getOnExpression() == null) {
										join.setOnExpression(CCJSqlParserUtil.parseCondExpression(condExpr));
									} else {
										AndExpression and = new AndExpression(join.getOnExpression(), CCJSqlParserUtil.parseCondExpression(condExpr));
										join.setOnExpression(and);
									}
								}
							}
						}
					} catch(Exception e) {
						if(log.isDebugEnabled()) {
							log.debug("当前sql没有join的部分！");
						}
					}
				}
			}
			if(realRuls == null) {
				return sql; // 没有合适规则直接退出
			}

			if(sql.indexOf("limit ?,?") != -1 && select.toString().indexOf("LIMIT ? OFFSET ?") != -1) {
				sql = select.toString().replace("LIMIT ? OFFSET ?", "limit ?,?");
			} else {
				sql = select.toString();
			}

		} catch(JSQLParserException e) {
			log.error("process sql error - sql: \r\n{}\r\n\r\nexception:\r\n{}", sql, e);
		}
		return sql;
	}

	/**
	 * 替换对应的占位符
	 *
	 * @param exps
	 * @param table
	 * @param tableAlias
	 * @param user
	 * @return
	 */
	private String _processPlaceholder(String exps, String table, String tableAlias, BranchUser user) {
		if(exps.contains("{uid}")) {
			exps = exps.replace("{uid}", "'" + user.getUuid() + "'");
		}
		if(exps.contains("{sid}")) {
			exps = exps.replace("{sid}", "'" + user.getSupplierUuid() + "'");
		}
		if(exps.contains("{rid}")) {
			exps = exps.replace("{rid}", "'" + user.getRetailerUuid() + "'");
		}
		if(exps.contains("{bid}")) {
			exps = exps.replace("{bid}", "'" + user.getBranchUuid() + "'");
		}
		boolean changedT = false;
		table = StringUtils.isNotBlank(tableAlias) ? null : table; //若别名不为空，则忽略无别名的匹配规则
		if(StringUtils.isNotBlank(table) && exps.contains("{t}")) {
			exps = exps.replace("{t}", table);
			changedT = true;
		}
		if(StringUtils.isNotBlank(tableAlias) && exps.contains("{t.a}")) {
			exps = exps.replace("{t.a}", tableAlias);
			changedT = true;
		}
		return changedT ? exps : null;
	}

	private String processDeleteSql(String sql, BranchUser user) {
		return sql;
	}
}
